#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

#include "list.h"
#include "types.h"
#include "inter_main.h"
#include "core_main.h"

struct list_head domain_list;

/*
 * initialize the domain_list head.
 */
void core_init()
{
	INIT_LIST_HEAD( &domain_list );
}

/*
 * reclaim all resources used in the core data structure.
 */
void core_exit()
{
	reclaim_core_data();
	printf( "Resouces used in the CORE_DATA module are reclaimed.\n" );
}

/*
 * insert time slice record to the core data structure.
 */
int insert_time_slice( unsigned dom_id, unsigned vcpu_id, struct time_slice * ptime_slice )
{
	struct domain *pdomain, *res;
	struct vcpu* pvcpu, *res_vcpu;
	struct list_head *iter;

	if ( (pdomain = find_domain_by_id(dom_id)) == NULL ){
		pdomain = malloc( sizeof(struct domain) );
		ASSERT( pdomain != NULL );
		memset( pdomain, 0, sizeof(struct domain) );
		INIT_LIST_HEAD( &pdomain->vcpu_list );
		INIT_LIST_HEAD( &pdomain->domain_list_elem );
		pdomain->domain_id = dom_id;
		pdomain->vcpu_count = 0;

		//the domain list should be sorted when adding new node
		if ( list_empty( &domain_list ) )
			list_add_tail( &pdomain->domain_list_elem, &domain_list );
		else{
			list_for_each( iter, &domain_list ){
				res = list_entry( iter, struct domain, domain_list_elem );
				if ( dom_id < res->domain_id ) break;
				if( list_is_last( iter, &domain_list ) ){
					list_add_tail( &pdomain->domain_list_elem, &domain_list );
					goto handle_vcpu;
				}
			}
			list_add_before( &pdomain->domain_list_elem, iter );
		}
	}

handle_vcpu:	
	ASSERT( pdomain != NULL );

	if ( (pvcpu = find_vcpu_by_id(pdomain, vcpu_id)) == NULL ){
		pvcpu = malloc( sizeof(struct vcpu) );
		ASSERT( pvcpu != NULL );
		memset( pvcpu, 0, sizeof(struct vcpu) );
		pvcpu->vcpu_id = vcpu_id;
		INIT_LIST_HEAD( &pvcpu->time_slice_list );
		INIT_LIST_HEAD( &pvcpu->vcpu_list_elem );

		//the VCPU list should also be sorted when adding new node
		if ( list_empty( &pdomain->vcpu_list ) )
			list_add_tail( &pvcpu->vcpu_list_elem, &pdomain->vcpu_list );
		else{
			list_for_each( iter, &pdomain->vcpu_list ){
				res_vcpu = list_entry( iter, struct vcpu, vcpu_list_elem );
				if ( vcpu_id < res_vcpu->vcpu_id ) break;
				if( list_is_last( iter, &pdomain->vcpu_list ) ){
					list_add_tail( &pvcpu->vcpu_list_elem, &pdomain->vcpu_list );
					goto handle_time_slice;
				}
			}
			list_add_before( &pvcpu->vcpu_list_elem, iter );
		}
	}

handle_time_slice:
	ASSERT( pvcpu != NULL );
	//should add the time slice record now.
	add_time_slice( pvcpu, ptime_slice );

	return 0;
}

/*
 * browse domain_list to find the domain with the dom_id, 
 * return NULL, if not found.
 */
struct domain* find_domain_by_id( unsigned int dom_id )
{
	struct list_head *iter;
	struct domain* res;

	if ( list_empty( &domain_list ) ) return NULL;

	list_for_each( iter, &domain_list )
	{
		res = list_entry( iter, struct domain, domain_list_elem );
		if (res->domain_id == dom_id ) return res;
	}

	return NULL;
}

/*
 * find the VCPU with vcpu_id.
 */
struct vcpu* find_vcpu_by_id( struct domain* pdom, unsigned int vcpu_id )
{
	struct list_head *iter;
	struct vcpu* res;

	if( list_empty( &pdom->vcpu_list )) return NULL;

	list_for_each( iter, &pdom->vcpu_list )
	{
		res = list_entry( iter, struct vcpu, vcpu_list_elem );
		if (res->vcpu_id == vcpu_id) return res;
	}

	return NULL;
}

/*
 * add the time slice record to the time slice list of the vcpu.
 */
void add_time_slice( struct vcpu* pvcpu, struct time_slice * ptime_slice )
{
	struct list_head * iter;
	struct time_slice* iter_slice;

	if ( list_empty( &pvcpu->time_slice_list )){
		list_add_tail( &ptime_slice->time_slice_elem, &pvcpu->time_slice_list );
		pvcpu->num_sched ++;
		return;
	}
	
	list_for_each( iter, &pvcpu->time_slice_list )
	{
		iter_slice = list_entry( iter, struct time_slice, time_slice_elem );
		if( ptime_slice->start_tsc < iter_slice->start_tsc ) break;

		//the last one, now.
		if( list_is_last( iter, &pvcpu->time_slice_list ) ){
			list_add_tail( &ptime_slice->time_slice_elem, &pvcpu->time_slice_list );
			pvcpu->num_sched ++;
			return;
		}
	}
	list_add_before( &ptime_slice->time_slice_elem, iter );
	pvcpu->num_sched ++;
}

/*
 * show one time slice. very simple function.
 * We now define the STANDARD OUTPUT OF a TIME SLICE as follows:
 * TimeSlice:<DOM_ID:VCPU_ID>\tPCPU_ID\tSCHED_IN_TSC\tSCHED_OUT_TSC\tSCHED_IN_TYPE\tSCHED_OUT_TYPE
 * Note: This format is used to show a time slice, and it should be not usable on plotting.
 */
void print_one_time_slice(struct time_slice* ptime_slice, char* prefix)
{
	printf( "%s:<%d:%d>\t%d\t%llu\t%llu\t", prefix,
		ptime_slice->domain_id, ptime_slice->vcpu_id, ptime_slice->pcpu_number,
		ptime_slice->start_tsc, ptime_slice->term_tsc );

	switch( ptime_slice->sched_in_type ){
		case SCHED_IN_NORMAL:
			printf( "SCHED_IN_NORMAL\t" );
			break;
		case SCHED_IN_WAKE:
			printf( "SCHED_IN_WAKE\t" );
			break;
	}

	switch( ptime_slice->sched_out_type ){
		case SCHED_OUT_NORMAL:
			printf( "SCHED_OUT_NORMAL\t" );
			break;
		case SCHED_OUT_BLOCK:
			printf( "SCHED_OUT_BLOCK\t" );
			break;
		case SCHED_OUT_YIELD:
			printf( "SCHED_OUT_YIELD\t" );
			break;
		case SCHED_OUT_PREEMPT:
			printf( "SCHED_OUT_PREEMPT\t" );
			break;

	}

	printf( "\n" );
	fflush( stdout );
}

/*
 * verify_core_data -- Doing the following checks on the core data structure:
 * 1) verify if the time slices of each VCPUs are orderd by time
 * 2) verify the block and wake logic, i.e., if a VCPU was blocked in
 * the previous time slice, it should be waken in the succeeding time slice.
 *
 * Ret:
 * 0 means all passed
 * -1 means minor errors that may caused by lost records
 * -2 means serious error, and the system should not proceed.
 */
int verify_core_data( void )
{
	struct list_head* iter_domain, *iter_vcpu;
	struct list_head* iter_time_slice, *iter_time_slice_next;
	struct domain* pdomain;
	struct vcpu* pvcpu;
	struct time_slice* ptime_slice, *ptime_slice_next;
	int time_slice_sn;

	list_for_each( iter_domain, &domain_list ){
		pdomain = list_entry( iter_domain, struct domain, domain_list_elem );

		list_for_each( iter_vcpu, &pdomain->vcpu_list ){
			pvcpu = list_entry( iter_vcpu, struct vcpu, vcpu_list_elem );

			time_slice_sn = 0;
			list_for_each_safe( iter_time_slice, iter_time_slice_next, &pvcpu->time_slice_list ){
				ptime_slice = list_entry( iter_time_slice, struct time_slice, time_slice_elem );
				ptime_slice_next = list_entry( iter_time_slice_next, struct time_slice, time_slice_elem );

				//the list has only one item, or browsed to the end of the list
				if( list_is_last( iter_time_slice, &pvcpu->time_slice_list ) ) break;

				//verify the time relationship
				if( ptime_slice->term_tsc > ptime_slice_next->term_tsc ){
					printf( "Miss ordering----\n" );
					print_one_time_slice( ptime_slice, "Previous time slice:" );
					print_one_time_slice( ptime_slice_next, "Next time slice:" );
					return -2;
				}

				//these two time slice should NOT overlap!
				if( ptime_slice->term_tsc > ptime_slice_next->start_tsc ){
					printf( "Time slice overlapping----\n" );
					print_one_time_slice( ptime_slice, "Previous time slice:" );
					print_one_time_slice( ptime_slice_next, "Next time slice:" );
					return -2;
				}

				//verify the block and wake logic
				if( ptime_slice->sched_out_type == SCHED_OUT_BLOCK )
					if( ptime_slice_next->sched_in_type != SCHED_IN_WAKE ){
						//the logic does not match
#ifdef AUTO_FIXING_TS_LOGIC
						//we should fix this logic problem. After fixing, the computing on wake_to_sched_in
						//will NOT be accurate!
						printf( "Fixing BLOCK to WAKE relation between time slices (wake_to_sched_in IS NOT ACCURATE after fixing)\n" );
						print_one_time_slice( ptime_slice, "Previous time slice:" );
						print_one_time_slice( ptime_slice_next, "Next time slice:" );
						ptime_slice_next->sched_in_type = SCHED_IN_WAKE;
						ptime_slice_next->sched_in.wake_data.wake_PCPU = ptime_slice_next->pcpu_number;
						ptime_slice_next->sched_in.wake_data.wake_tsc = ptime_slice_next->start_tsc;
#else
						printf( "BLOCK and WAKE logic does not match.\n" );
						print_one_time_slice( ptime_slice, "Previous time slice:" );
						print_one_time_slice( ptime_slice_next, "Next time slice:" );
#endif
					}
				time_slice_sn ++;
			}
		}
	}
	return 0;
}

/*
 * only list the VCPUs and the brief statistics.
 */
void brief_core_data( void )
{
	struct list_head *iter_domain, *iter_vcpu;
	struct domain* pdomain;
	struct vcpu* pvcpu;
	int	vcpu_no;

	vcpu_no = 0;
	printf( "VCPUs brief list:\n" );
	printf( "SN\tDOM_ID\tVCPU_ID\tTIME_SLICES_COUNT\n" );

	list_for_each( iter_domain, &domain_list ){
		pdomain = list_entry( iter_domain, struct domain, domain_list_elem );
		list_for_each( iter_vcpu, &pdomain->vcpu_list ){
			pvcpu = list_entry( iter_vcpu, struct vcpu, vcpu_list_elem );
			printf( "%d\t%d\t%d\t%d\n", vcpu_no++, 
				pdomain->domain_id, pvcpu->vcpu_id, pvcpu->num_sched );
		}
	}
}

/*
 * browse the core data structure, print all possible time slices.
 */
void browse_core_data( void )
{
	struct list_head* iter_domain, *iter_vcpu, *iter_time_slice;
	struct domain* pdomain;
	struct vcpu* pvcpu;
	struct time_slice* ptime_slice;
	int core_rec_sn=0;

	printf( "SN\tDOM_ID\tVCPU_ID\tPCPU_ID\tSCHED_IN_TSC\tSCHED_OUT_TSC\tSCHED_IN_TYPE\tSCHED_OUT_TYPE\n" );

	list_for_each( iter_domain, &domain_list ){
		pdomain = list_entry( iter_domain, struct domain, domain_list_elem );
		list_for_each( iter_vcpu, &pdomain->vcpu_list ){
			pvcpu = list_entry( iter_vcpu, struct vcpu, vcpu_list_elem );
			list_for_each( iter_time_slice, &pvcpu->time_slice_list ){
				ptime_slice = list_entry( iter_time_slice, struct time_slice, time_slice_elem );
		        printf( "%d\t%d\t%d\t%d\t%llu\t%llu\t%d\t%d\n", 
					core_rec_sn++, 
					pdomain->domain_id,
					pvcpu->vcpu_id,
					ptime_slice->pcpu_number,
		            ptime_slice->start_tsc, 
        		    ptime_slice->term_tsc, 
		            ptime_slice->sched_in_type,
					ptime_slice->sched_out_type );
			}
		}
	}
}

/*
 * free the core data structure
 */
void reclaim_core_data( void )
{
	struct list_head* iter_domain, *iter_vcpu, *iter_time_slice;
	struct list_head* iter_domain_next, *iter_vcpu_next, *iter_time_slice_next;
	struct domain* pdomain;
	struct vcpu* pvcpu;
	struct time_slice* ptime_slice;

	//reclaim domain records
	list_for_each_safe( iter_domain, iter_domain_next, &domain_list ){
		pdomain = list_entry( iter_domain, struct domain, domain_list_elem );
		//reclaim vcpu records
		list_for_each_safe( iter_vcpu, iter_vcpu_next, &pdomain->vcpu_list ){
			pvcpu = list_entry( iter_vcpu, struct vcpu, vcpu_list_elem );
			//reclaim time slice records
			list_for_each_safe( iter_time_slice, iter_time_slice_next, &pvcpu->time_slice_list ){
				ptime_slice = list_entry( iter_time_slice, struct time_slice, time_slice_elem );
				list_del( iter_time_slice );
				free( ptime_slice);
			}
			ASSERT( list_empty( &pvcpu->time_slice_list ) );
			list_del( iter_vcpu );
			free( pvcpu );
		}
		ASSERT( list_empty( &pdomain->vcpu_list ) );
		list_del( iter_domain );
		free( pdomain );
	}
	
	ASSERT( list_empty( &domain_list ) );

}

//
// test the correctness of the routines pertaining core data structure manipulation
// SHOULD BE REMOVED AFTER TESTING
//
void test_add_time_slices( void )
{
	int i, j; //domain id and vcpu id respectively
	int t;
	struct time_slice* ptime_slice;

	for( i=0; i<2; i++ )
		for( j=0; j<2; j++ ){
			for( t=5; t<7; t++ ){
				ptime_slice = malloc( sizeof(struct time_slice) );
				ASSERT( ptime_slice != NULL );
				memset( ptime_slice, 0, sizeof(struct time_slice) );

				ptime_slice->start_tsc = (unsigned long long)t;
				INIT_LIST_HEAD( &ptime_slice->time_slice_elem );

				insert_time_slice( i, j, ptime_slice );
			}
			for( t=4; t>2; t-- ){
				ptime_slice = malloc( sizeof(struct time_slice) );
				ASSERT( ptime_slice != NULL );
				memset( ptime_slice, 0, sizeof(struct time_slice) );

				ptime_slice->start_tsc = (unsigned long long)t;
				INIT_LIST_HEAD( &ptime_slice->time_slice_elem );

				insert_time_slice( i, j, ptime_slice );
			}
		}

	printf( "test_add_time_slices finished.\n" );
}

